
 package tom.kit.clazz;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;


 /**
  * 泛型方法的工具类，可以用来处理bridged method。
  *
  *
  * @author Service Platform Architecture Team (spat@58.com)
  */
 public abstract class BridgeMethodResolver {

     /**
      * 通过参数Method找到我们调用的原始Method
      * java 通过bridge方式来实现泛型。
      *
      *
      * @param bridgeMethod
      * @return the original method
      */
     public static Method findBridgedMethod(Method bridgeMethod) {
         if (bridgeMethod == null || !bridgeMethod.isBridge()) {
             return bridgeMethod;
         }
         // Gather all methods with matching name and parameter size.
         List<Method> candidateMethods = new ArrayList<Method>();
         Method[] methods = ReflectionUtils.getAllDeclaredMethods(bridgeMethod.getDeclaringClass());
         for (Method candidateMethod : methods) {
             if (isBridgedCandidateFor(candidateMethod, bridgeMethod)) {
                 candidateMethods.add(candidateMethod);
             }
         }
         // Now perform simple quick check.
         if (candidateMethods.size() == 1) {
             return candidateMethods.get(0);
         }
         // Search for candidate match.
         Method bridgedMethod = searchCandidates(candidateMethods, bridgeMethod);
         if (bridgedMethod != null) {
             // Bridged method found...
             return bridgedMethod;
         }
         else {
             // A bridge method was passed in but we couldn't find the bridged method.
             // Let's proceed with the passed-in method and hope for the best...
             return bridgeMethod;
         }
     }


     private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
         if (candidateMethods.isEmpty()) {
             return null;
         }
         Map<TypeVariable, Type> typeParameterMap = GenericTypeResolver.getTypeVariableMap(bridgeMethod.getDeclaringClass());
         Method previousMethod = null;
         boolean sameSig = true;
         for (Method candidateMethod : candidateMethods) {
             if (isBridgeMethodFor(bridgeMethod, candidateMethod, typeParameterMap)) {
                 return candidateMethod;
             }
             else if (previousMethod != null) {
                 sameSig = sameSig &&
                         Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
             }
             previousMethod = candidateMethod;
         }
         return (sameSig ? candidateMethods.get(0) : null);
     }


     private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) {
         return (!candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) &&
                 candidateMethod.getName().equals(bridgeMethod.getName()) &&
                 candidateMethod.getParameterTypes().length == bridgeMethod.getParameterTypes().length);
     }


     static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Map<TypeVariable, Type> typeVariableMap) {
         if (isResolvedTypeMatch(candidateMethod, bridgeMethod, typeVariableMap)) {
             return true;
         }
         Method method = findGenericDeclaration(bridgeMethod);
         return (method != null && isResolvedTypeMatch(method, candidateMethod, typeVariableMap));
     }


     private static Method findGenericDeclaration(Method bridgeMethod) {
         // Search parent types for method that has same signature as bridge.
         Class superclass = bridgeMethod.getDeclaringClass().getSuperclass();
         while (!Object.class.equals(superclass)) {
             Method method = searchForMatch(superclass, bridgeMethod);
             if (method != null && !method.isBridge()) {
                 return method;
             }
             superclass = superclass.getSuperclass();
         }

         // Search interfaces.
         Class[] interfaces = ReflectUtil.getAllInterfacesForClass(bridgeMethod.getDeclaringClass());
         for (Class ifc : interfaces) {
             Method method = searchForMatch(ifc, bridgeMethod);
             if (method != null && !method.isBridge()) {
                 return method;
             }
         }

         return null;
     }


     private static boolean isResolvedTypeMatch(
             Method genericMethod, Method candidateMethod, Map<TypeVariable, Type> typeVariableMap) {

         Type[] genericParameters = genericMethod.getGenericParameterTypes();
         Class[] candidateParameters = candidateMethod.getParameterTypes();
         if (genericParameters.length != candidateParameters.length) {
             return false;
         }
         for (int i = 0; i < genericParameters.length; i++) {
             Type genericParameter = genericParameters[i];
             Class candidateParameter = candidateParameters[i];
             if (candidateParameter.isArray()) {
                 // An array type: compare the component type.
                 Type rawType = GenericTypeResolver.getRawType(genericParameter, typeVariableMap);
                 if (rawType instanceof GenericArrayType) {
                     if (!candidateParameter.getComponentType().equals(
                             GenericTypeResolver.resolveType(((GenericArrayType) rawType).getGenericComponentType(), typeVariableMap))) {
                         return false;
                     }
                     break;
                 }
             }
             // A non-array type: compare the type itself.
             Class resolvedParameter = GenericTypeResolver.resolveType(genericParameter, typeVariableMap);
             if (!candidateParameter.equals(resolvedParameter)) {
                 return false;
             }
         }
         return true;
     }


     private static Method searchForMatch(Class type, Method bridgeMethod) {
         return ReflectionUtils.findMethod(type, bridgeMethod.getName(), bridgeMethod.getParameterTypes());
     }

 }

